package com.hup.winHolderService.config;

import com.hup.winHolderService.framework.exception.AppHiddenMsgException;
import com.hup.winHolderService.framework.exception.AppMsgException;
import com.hup.winHolderService.framework.exception.AppServerException;
import com.hup.winHolderService.framework.model.ExceptionResult;
import com.hup.winHolderService.framework.model.ExceptionResult.BaseResultException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.PropertyAccessException;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.multipart.MultipartException;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;

/**
 * @author hugan
 * @date 2021/3/7
 */
@Slf4j
@ControllerAdvice
public class ControllerExceptionHandler {

    private static List<ErrHandler> errHandlers;

    public ControllerExceptionHandler() {
        errHandlers = new ArrayList<>();
        errHandlers.add(new ApiArgHandler());
    }

    @ExceptionHandler(Throwable.class)
    public ResponseEntity<ExceptionResult> handleException(Throwable e, HttpServletRequest request) {
        BaseResultException e2 = getResultException(e, request);
        e2.doLog();
        return e2.toResult();
    }

    public static BaseResultException getResultException(Throwable e, HttpServletRequest request) {
        BaseResultException e2 = null;
        if (e instanceof BaseResultException) {
            //业务代码 主动抛的异常
            e2 = ((BaseResultException) e);
        } else {
            //框架抛的异常, 或业务代码未捕获的异常
            for (ErrHandler errHandler : errHandlers) {
                e2 = errHandler.handleException(e, request);
                if (e2 != null) break;
            }
            if (e2 == null) e2 = new AppServerException("未处理的异常", e);
        }
        return e2;
    }

    private interface ErrHandler {
        /**
         * 处理异常,如打印日志;并返回ErrorMsgException
         *
         * @return notNull-异常被处理,
         */
        BaseResultException handleException(Throwable e, HttpServletRequest request);
    }

    /**
     * 请求体异常:入参绑定异常, Method异常等...
     * .一般返回 AppHiddenMsgException: 不给前端"撞"api
     * .@Validated 返回 AppMsgException: 前端可以收到字段校验提醒
     */
    private static class ApiArgHandler implements ErrHandler {

        @Override
        public BaseResultException handleException(Throwable e, HttpServletRequest request) {
            if (e instanceof HttpMessageConversionException) {
                //req.ContentType不匹配导致服务端收不到入参
                return new AppHiddenMsgException("入参异常");
            } else if (e instanceof HttpRequestMethodNotSupportedException) {
                //get/post...方法不匹配
                return new AppHiddenMsgException(e.getMessage());
            } else if (e instanceof MissingServletRequestParameterException) {
                //get请求没RequestParam入参
                return new AppHiddenMsgException(e.getMessage());
            } else if (e instanceof MethodArgumentNotValidException) {
                //@Validated校验异常
                return new AppMsgException(getBindingMsg(e, ((MethodArgumentNotValidException) e).getBindingResult()));
            } else if (e instanceof BindException) {
                //@Validated校验异常
                return new AppMsgException(getBindingMsg(e, ((BindException) e).getBindingResult()));
            } else if (e instanceof MultipartException || e instanceof HttpMediaTypeNotSupportedException) {
                /*
                MultipartException:Failed to parse multipart servlet request
                    .请求方设置了请求格式是'multipart/form-data',但请求体中没有指定的'boundary'字符标记
                HttpMediaTypeNotSupportedException:
                    .后台controller使用RequestBody,但请求方的contentType是'multipart/form-data'
                 */
                return new AppHiddenMsgException("请求体异常");
            } else if (e instanceof PropertyAccessException) {
                /*
                restful风格导致的 "歧义"异常, 是坑: 所以不建议restful
                如有接口: GET /path1/path2/{id},  POST /path1/path2/save
                当请求 GET /path1/path2/save 时, 本应是 HttpRequestMethodNotSupportedException,但spring会误报成 "save字符串"不能转成"id, Integer"
                 */
                return new AppHiddenMsgException(request.getRequestURI() + ": " + e.getMessage());
            }
            return null;
        }

        private String getBindingMsg(Throwable e, BindingResult bindingResult) {
            List<ObjectError> allErrors = bindingResult.getAllErrors();
            String msg;
            if (allErrors.isEmpty()) {
                msg = e.getMessage();
            } else {
                //allErrors排序是随机的,所以只取第一个错误信息
                msg = allErrors.get(0).getDefaultMessage();
            }
            if (msg != null && msg.contains("Failed to convert property")) {
                msg = "入参类型异常" + msg;
            }
            return msg;
        }
    }

}
